package jp.tkym.labs.util.prop;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class PropertyResolver<T> {
	private Class<T> cls;
	private Constructor<T> constructor;
	
	private Map<String, Method> getterMap;
	private Map<String, Method> setterMap;
	private Map<String, Class<?>> typeMap;
	private List<String> propNamesList;
	
	PropertyResolver(Class<T> cls){
		this.cls = cls;
		init();
	}
	
	private static final String GET = "get";
	private static final String SET = "set";
	private static final String IS = "is";
	private static final String GET_CLASS = "getClass";
	
	void init(){
		getterMap = new HashMap<String, Method>();
		setterMap = new HashMap<String, Method>();
		typeMap   = new HashMap<String, Class<?>>();
		propNamesList = new ArrayList<String>();
		
		Method[] methodArray = cls.getMethods();
		
		for(int i=0; i<methodArray.length; i++){
			Method method = methodArray[i];
			
			String methodName = method.getName();
			
			if(methodName.startsWith(GET) && methodName.length()>3 && !methodName.equals(GET_CLASS)){//getXxx
				String propName = propertyName(methodName);
				getterMap.put(propName, method);
				typeMap.put(propName, method.getReturnType());
				
			}else if(methodName.startsWith(SET) && methodName.length()>3){//setXxx
				String propName = propertyName(methodName);
				setterMap.put(propName, method);
				
			}else if(methodName.startsWith(IS) && methodName.length()>2 && method.getReturnType()==boolean.class){
				String propName = propertyName(" "+methodName);
				getterMap.put(propName, method);
				typeMap.put(propName, boolean.class);
				
			}
		}
		
		List<String> keepList = new ArrayList<String>();
		while(!propNamesList.isEmpty()){
			String name = propNamesList.get(0);
			propNamesList.remove(0);
			if(propNamesList.contains(name)){
				keepList.add(name);
				propNamesList.remove(name);
			}
		}
		propNamesList = keepList;
	}
	
	private String propertyName(String methodName){
		char[] charArray = methodName.substring(3).toCharArray();
		charArray[0] = Character.toLowerCase(charArray[0]);
		String propName = new String(charArray);
		propNamesList.add(propName);
		return propName;
	}
	
	public List<String> propertyNames(){
		return propNamesList;
	}
	
	public Class<?> getTypeOf(String propertyName){
		return typeMap.get(propertyName);
	}
	
	public boolean contains(String propertyName){
		return propNamesList.contains(propertyName);
	}
	
	public void set(T bean, String propertyName, Object value){
		Method setter = setterMap.get(propertyName);
		try {
			setter.invoke(bean, value);
		} catch (IllegalArgumentException e) {
			throw new PropertyResolverException(errorMessageOnSet(bean, propertyName, value),e);
		} catch (IllegalAccessException e) {
			throw new PropertyResolverException(errorMessageOnSet(bean, propertyName, value),e);
		} catch (InvocationTargetException e) {
			throw new PropertyResolverException(errorMessageOnSet(bean, propertyName, value),e);
		}
	}
	
	private String errorMessageOnSet(T bean, String propertyName, Object value){
		return 
			"exception on set. " +
			"bean Class=" + bean.getClass().getName()+", "+
			"propertyName=" + propertyName + ", " +
			"value= " + value.toString();
	}
	
	public Object get(T bean, String propertyName){
		Method getter = getterMap.get(propertyName);
		try {
			return getter.invoke(bean);
		} catch (IllegalArgumentException e) {
			throw new PropertyResolverException(e);
		} catch (IllegalAccessException e) {
			throw new PropertyResolverException(e);
		} catch (InvocationTargetException e) {
			throw new PropertyResolverException(e);
		}
	}

	void initConstructor(){
		try {
			constructor = this.cls.getConstructor();
		} catch (SecurityException e) {
			throw new PropertyResolverException(e);
		} catch (NoSuchMethodException e) {
			throw new PropertyResolverException(e);
		}
	}
	
	public T createInstance(){
		if(constructor == null){
			initConstructor();
		}
		try {
			return constructor.newInstance();
		} catch (Exception e) {
			throw new PropertyResolverException(e);
		}
	}
}